/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


// s_streaming.cpp -- background music and cinematic streaming


#include "s_local.h"
extern "C" {
#include "vorbis/vorbisfile.h"
}

#define BG_MUSIC_BUFFERS			4
#define BG_MUSIC_BUFFER_SIZE		32768

typedef struct {
	char				name[MAX_OSPATH];

	fileHandle_t		file;
	int					header;

	int					rate;
	uint				format;

	OggVorbis_File *	oggVorbisFile;
} bgMusic_t;

static bgMusic_t		s_bgMusic;

static uint				s_streamingSource;


/*
 ==============================================================================

 BACKGROUND MUSIC

 ==============================================================================
*/


/*
 ==================
 S_ReadOGG
 ==================
*/
static size_t S_ReadOGG (void *buffer, size_t size, size_t count, void *data){

	if (!size || !count)
		return 0;

	return FS_Read(buffer, size * count, s_bgMusic.file) / size;
}

/*
 ==================
 S_SeekOGG
 ==================
*/
static int S_SeekOGG (void *data, ogg_int64_t offset, int origin){

	switch (origin){
	case SEEK_SET:
		FS_Seek(s_bgMusic.file, (int)offset, FS_SEEK_SET);
		break;
	case SEEK_CUR:
		FS_Seek(s_bgMusic.file, (int)offset, FS_SEEK_CUR);
		break;
	case SEEK_END:
		FS_Seek(s_bgMusic.file, (int)offset, FS_SEEK_END);
		break;
	default:
		return -1;
	}

	return 0;
}

/*
 ==================
 S_CloseOGG
 ==================
*/
static int S_CloseOGG (void *data){

	return 0;
}

/*
 ==================
 S_TellOGG
 ==================
*/
static long S_TellOGG (void *data){

	return FS_Tell(s_bgMusic.file);
}

/*
 ==================
 S_StreamBackgroundMusic
 ==================
*/
static void S_StreamBackgroundMusic (void){

	byte	data[BG_MUSIC_BUFFER_SIZE];
	uint	buffer;
	int		queued, state;
	int		size, read, dummy;

	if (s_skipStreaming->integerValue)
		return;

	if (!s_bgMusic.file || !s_musicVolume->floatValue)
		return;			// Not playing or muted

	if (s_logFile->integerValue)
		QAL_LogPrintf("----- S_StreamBackgroundMusic -----\n");

	// Make sure we always have a few buffers in the queue
	qalGetSourcei(s_streamingSource, AL_BUFFERS_QUEUED, &queued);

	while (queued < BG_MUSIC_BUFFERS){
		size = 0;

		// Stream from disk
		while (size < sizeof(data)){
			read = ov_read(s_bgMusic.oggVorbisFile, (char *)(data + size), sizeof(data) - size, 0, 2, 1, &dummy);
			if (!read){
				// The end of file has been reached, so restart the music file
				// skipping over the header
				ov_raw_seek(s_bgMusic.oggVorbisFile, (ogg_int64_t)s_bgMusic.header);

				// Try streaming again
				read = ov_read(s_bgMusic.oggVorbisFile, (char *)(data + size), sizeof(data) - size, 0, 2, 1, &dummy);
			}

			if (read <= 0){
				Com_DPrintf(S_COLOR_RED "S_StreamBackgroundMusic: failed to read from OGG stream (%s)\n", s_bgMusic.name);

				S_StopBackgroundMusic();
				return;
			}

			size += read;
		}

		// Upload a new buffer
		qalGenBuffers(1, &buffer);
		qalBufferData(buffer, s_bgMusic.format, data, size, s_bgMusic.rate);

		// Queue it
		qalSourceQueueBuffers(s_streamingSource, 1, &buffer);

		queued++;
	}

	// Update volume
	qalSourcef(s_streamingSource, AL_GAIN, s_musicVolume->floatValue);

	// Make sure the source is playing
	qalGetSourcei(s_streamingSource, AL_SOURCE_STATE, &state);

	if (state != AL_PLAYING)
		qalSourcePlay(s_streamingSource);
}

/*
 ==================
 S_PlayBackgroundMusic
 ==================
*/
void S_PlayBackgroundMusic (const char *name){

	vorbis_info		*header;
	ov_callbacks	callbacks = {S_ReadOGG, S_SeekOGG, S_CloseOGG, S_TellOGG};

	if (!s_streamingSource)
		return;

	// If already playing a music file, stop it
	if (s_bgMusic.file)
		S_StopBackgroundMusic();

	// Open the music file
	Str_Copy(s_bgMusic.name, name, sizeof(s_bgMusic.name));

	FS_OpenFile(name, &s_bgMusic.file, FS_READ);
	if (!s_bgMusic.file){
		Com_DPrintf(S_COLOR_YELLOW "S_PlayBackgroundMusic: couldn't find '%s'\n", name);
		return;
	}

	// Open the OGG stream
	s_bgMusic.oggVorbisFile = (OggVorbis_File *)Mem_Alloc(sizeof(OggVorbis_File), TAG_SOUND);

	if (ov_open_callbacks(&s_bgMusic, s_bgMusic.oggVorbisFile, NULL, 0, callbacks) < 0){
		Com_DPrintf(S_COLOR_YELLOW "S_PlayBackgroundMusic: couldn't open OGG stream (%s)\n", name);

		S_StopBackgroundMusic();
		return;
	}

	header = ov_info(s_bgMusic.oggVorbisFile, -1);

	if (header->channels != 1 && header->channels != 2){
		Com_DPrintf(S_COLOR_YELLOW "S_PlayBackgroundMusic: only mono and stereo OGG streams supported (%s)\n", name);

		S_StopBackgroundMusic();
		return;
	}

	s_bgMusic.header = (int)ov_raw_tell(s_bgMusic.oggVorbisFile);
	s_bgMusic.rate = header->rate;
	s_bgMusic.format = (header->channels == 2) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;
}

/*
 ==================
 S_StopBackgroundMusic
 ==================
*/
void S_StopBackgroundMusic (void){

	if (!s_streamingSource)
		return;

	if (!s_bgMusic.file)
		return;			// Not playing

	// Stop it
	qalSourceStop(s_streamingSource);

	// Free all the resources in use
	if (s_bgMusic.oggVorbisFile){
		ov_clear(s_bgMusic.oggVorbisFile);

		Mem_Free(s_bgMusic.oggVorbisFile);
	}

	if (s_bgMusic.file)
		FS_CloseFile(s_bgMusic.file);

	memset(&s_bgMusic, 0, sizeof(bgMusic_t));
}


/*
 ==============================================================================

 CINEMATICS

 ==============================================================================
*/


/*
 ==================
 S_StreamRawSamples
 ==================
*/
void S_StreamRawSamples (const void *data, int samples, int rate, int channels){

	uint	format, buffer;
	int		state, size;

	if (!s_streamingSource)
		return;

	if (s_skipStreaming->integerValue)
		return;

	if (s_logFile->integerValue)
		QAL_LogPrintf("----- S_StreamRawSamples -----\n");

	if (channels != 1 && channels != 2)
		Com_Error(false, "S_StreamRawSamples: stream is not mono or stereo");

	// Calculate buffer size
	size = samples * channels * 2;

	// Set buffer format
	if (channels == 2)
		format = AL_FORMAT_STEREO16;
	else
		format = AL_FORMAT_MONO16;

	// Upload a new buffer
	qalGenBuffers(1, &buffer);
	qalBufferData(buffer, format, data, size, rate);

	// Queue it
	qalSourceQueueBuffers(s_streamingSource, 1, &buffer);

	// Update volume
	qalSourcef(s_streamingSource, AL_GAIN, s_effectsVolume->floatValue);

	// Make sure the source is playing
	qalGetSourcei(s_streamingSource, AL_SOURCE_STATE, &state);

	if (state != AL_PLAYING)
		qalSourcePlay(s_streamingSource);
}


// ============================================================================


/*
 ==================
 S_PlayMusic_f
 ==================
*/
static void S_PlayMusic_f (void){

	char	name[MAX_OSPATH];

	if (Cmd_Argc() != 2){
		Com_Printf("Usage: playMusic <musicName>\n");
		return;
	}

	Str_Copy(name, Cmd_Argv(1), sizeof(name));
	Str_DefaultFilePath(name, sizeof(name), "music");
	Str_DefaultFileExtension(name, sizeof(name), ".ogg");

	// Play the music file
	S_PlayBackgroundMusic(name);
}

/*
 ==================
 S_StopMusic_f
 ==================
*/
static void S_StopMusic_f (void){

	S_StopBackgroundMusic();
}

/*
 ==================
 S_UpdateStreaming
 ==================
*/
void S_UpdateStreaming (void){

	uint	buffer;
	int		processed;

	if (!s_streamingSource)
		return;

	if (s_logFile->integerValue)
		QAL_LogPrintf("----- S_UpdateStreaming -----\n");

	// Unqueue and delete any processed buffers
	qalGetSourcei(s_streamingSource, AL_BUFFERS_PROCESSED, &processed);

	while (processed--){
		qalSourceUnqueueBuffers(s_streamingSource, 1, &buffer);
		qalDeleteBuffers(1, &buffer);
	}

	// Stream background music if needed
	S_StreamBackgroundMusic();
}

/*
 ==================
 S_InitStreaming
 ==================
*/
void S_InitStreaming (void){

	// Register our commands
	Cmd_AddCommand("playMusic", S_PlayMusic_f, "Plays a music file");
	Cmd_AddCommand("stopMusic", S_StopMusic_f, "Stops playing a music file");

	// Allocate the streaming source
	qalGenSources(1, &s_streamingSource);

	if (!s_streamingSource)
		return;

	// Set up the source
	qalSourcei(s_streamingSource, AL_BUFFER, 0);
	qalSourcei(s_streamingSource, AL_LOOPING, AL_FALSE);
	qalSourcei(s_streamingSource, AL_SAMPLE_OFFSET, 0);
	qalSourcei(s_streamingSource, AL_SOURCE_RELATIVE, AL_TRUE);

	qalSourcefv(s_streamingSource, AL_POSITION, vec3_origin.ToFloatPtr());
	qalSourcefv(s_streamingSource, AL_VELOCITY, vec3_origin.ToFloatPtr());

	qalSourcef(s_streamingSource, AL_GAIN, 1.0f);
	qalSourcef(s_streamingSource, AL_PITCH, 1.0f);
	qalSourcef(s_streamingSource, AL_REFERENCE_DISTANCE, 0.0f);
	qalSourcef(s_streamingSource, AL_MAX_DISTANCE, 0.0f);
	qalSourcef(s_streamingSource, AL_ROLLOFF_FACTOR, 0.0f);

	if (alConfig.efxAvailable){
		qalSourcei(s_streamingSource, AL_DIRECT_FILTER, 0);
		qalSource3i(s_streamingSource, AL_AUXILIARY_SEND_FILTER, 0, 0, 0);
	}
}

/*
 ==================
 S_ShutdownStreaming
 ==================
*/
void S_ShutdownStreaming (void){

	uint	buffer;
	int		processed;

	// Unregister our commands
	Cmd_RemoveCommand("playMusic");
	Cmd_RemoveCommand("stopMusic");

	// Clean up the source
	if (!s_streamingSource)
		return;

	qalSourceStop(s_streamingSource);

	qalGetSourcei(s_streamingSource, AL_BUFFERS_PROCESSED, &processed);

	while (processed--){
		qalSourceUnqueueBuffers(s_streamingSource, 1, &buffer);
		qalDeleteBuffers(1, &buffer);
	}

	// Free the streaming source
	qalDeleteSources(1, &s_streamingSource);

	s_streamingSource = 0;
}
